﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Timers;
using System.Diagnostics;
using System.Windows.Threading;
using System.Windows.Media.Animation;
using System.Reflection;

namespace Master
{
    /**
     * This abstract class befines the base functionality found in every single subclass of 'Car'.
     * 
     * @Creator A.J. Janssen
     * @Changes made by: 
     *      - J.J. Burgers
     *      - V.L. de Jager
     * */
    public abstract class Car : Image
    {
        /*****************
         * STATIC FIELDS *
         *****************/
        private static readonly int sleepTime = 40;

        /*************************
         * DEPENDENCY PROPERTIES *
         *************************/
        public static readonly DependencyProperty directionProperty, positionProperty, orientationProperty, speedProperty, scaleProperty;

        /**
         * Direction represents the direction this car will choice at the next intersection (Left, Right or Forward)
         * */
        public Direction Direction
        {
            set { SetValue(directionProperty, value); }
            get { return (Direction)GetValue(directionProperty); }
        }

        /**
         * The current position this car is located at, reflects the TopLeft point of this car.
         * */
        public Point Position
        {
            set { SetValue(positionProperty, value); }
            get { return (Point)GetValue(positionProperty); }
        }

        /**
         * Current orientation, in degrees, this car is moving in
         * */
        public int Orientation
        {
            set { SetValue(orientationProperty, value); }
            get { return (int)GetValue(orientationProperty); }
        }

        /**
         * The maximum speed. A car will always attempt to drive at this speed unless limited by the roads maxspeed or other, slower cars.
         * */
        public double Speed
        {
            set { SetValue(speedProperty, value); }
            get { return (double)GetValue(speedProperty); }
        }

        /**
         * This property sets the scale applied to the carImage. (1.0 = fullsize image, 0.5 half size image)
         * */
        public double Scale
        {
            set { SetValue(scaleProperty, value); }
            get { return (double)GetValue(scaleProperty); }
        }

        private static void positionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (Map.INSTANCE.ActualHeight == 0) return;
            Car thisCar = (Car)d; Rect bounds = thisCar.getTrueBounds();
            CarLeftMapEventArgs newEvent = null;
            if (bounds.X > Map.INSTANCE.ActualWidth && thisCar.Orientation >= 0 && thisCar.Orientation <= 180)
                newEvent = new CarLeftMapEventArgs(Car.CarLeftMapEvent, thisCar, Side.RIGHT);
            else if (bounds.X < 0 - bounds.Width && thisCar.Orientation >= 180)
                newEvent = new CarLeftMapEventArgs(Car.CarLeftMapEvent, thisCar, Side.LEFT);
            else if (bounds.Y > Map.INSTANCE.ActualHeight && thisCar.Orientation >= 90 && thisCar.Orientation <= 270)
                newEvent = new CarLeftMapEventArgs(Car.CarLeftMapEvent, thisCar, Side.BOTTOM);
            else if (bounds.Y < 0 - bounds.Height && (thisCar.Orientation <= 90 || thisCar.Orientation >= 270))
                newEvent = new CarLeftMapEventArgs(Car.CarLeftMapEvent, thisCar, Side.TOP);
            if (newEvent != null) thisCar.RaiseEvent(newEvent);
        }

        private static void orientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Car thisCar = (Car)d;
            TransformGroup transformGroup = new TransformGroup();
            transformGroup.Children.Add(new RotateTransform((int)e.NewValue));
            transformGroup.Children.Add(new ScaleTransform(thisCar.Scale, thisCar.Scale));
            thisCar.RenderTransform = transformGroup;
        }

        /**********
         * EVENTS *
         **********/
        public static readonly RoutedEvent CarLeftMapEvent; //--> GETS RAISED WHEN CAR LEAVES SCREEN

        public event CarLeftMapEventHandler CarLeftMap
        {
            add { AddHandler(Car.CarLeftMapEvent, value); }
            remove { RemoveHandler(Car.CarLeftMapEvent, value); }
        }

        /**********************
         * STATIC CONSTRUCTOR *
         **********************/
        static Car()
        {
            //DEPENDENCY PROPERTIES METADATA
            FrameworkPropertyMetadata positionPropertyMetadata = new FrameworkPropertyMetadata(new Point(-500, -500),
                new PropertyChangedCallback(Car.positionPropertyChanged));
            FrameworkPropertyMetadata orientationPropertyMetadata = new FrameworkPropertyMetadata(0,
                new PropertyChangedCallback(Car.orientationPropertyChanged));
            FrameworkPropertyMetadata speedPropertyMetadata = new FrameworkPropertyMetadata((double)10);
            FrameworkPropertyMetadata scalePropertyMetadata = new FrameworkPropertyMetadata((double)0.5);
            //DEPENDENCY PROPERTIES
            directionProperty = DependencyProperty.Register("Direction", typeof(Direction), typeof(Car));
            positionProperty = DependencyProperty.Register("Position", typeof(Point), typeof(Car), positionPropertyMetadata);
            orientationProperty = DependencyProperty.Register("Orientation", typeof(int), typeof(Car), orientationPropertyMetadata);
            speedProperty = DependencyProperty.Register("Speed", typeof(double), typeof(Car), speedPropertyMetadata);
            scaleProperty = DependencyProperty.Register("Scale", typeof(double), typeof(Car), scalePropertyMetadata);
            //EVENTS
            Car.CarLeftMapEvent = EventManager.RegisterRoutedEvent("CarLeftMap",
                RoutingStrategy.Direct, typeof(CarLeftMapEventHandler), typeof(Car));
        }

        /***************
         * CONSTRUCTOR *
         ***************/
        public Car()
        {
            initTransformOrigin(); //make sure any rotations or other transforms origin at the center of the car
            initCarImage();
            initRenderTransforms(); //Sets the current transforms (rotation and scale)
            bindPosition(); //bind the Position property of this car to Map.Left and Map.Top --> if the position is changed Map.Top and Map.Left will be automatically updated
            alterSpeed(); //change the default speed by a specific range
            Canvas.SetZIndex(this, 2);
        }

        /******************
         * INITIALIZATION *
         ******************/
        private void initTransformOrigin()
        {
            RenderTransformOrigin = new Point(0.5, 0.5);
        }

        /**
         * The implementation of this abstract method should set and scale the image to be used for this car
         * */
        protected abstract void initCarImage();

        protected void initRenderTransforms()
        {
            TransformGroup transformGroup = new TransformGroup();
            transformGroup.Children.Add(new RotateTransform(Orientation));
            transformGroup.Children.Add(new ScaleTransform(Scale, Scale));
            RenderTransform = transformGroup;
        }

        private void bindPosition()
        {
            Binding Xbinding = new Binding();
            Xbinding.Source = this;
            Xbinding.Path = new PropertyPath("Position.X");
            Xbinding.Mode = BindingMode.TwoWay;
            this.SetBinding(Map.LeftProperty, Xbinding);

            Binding Ybinding = new Binding();
            Ybinding.Source = this;
            Ybinding.Path = new PropertyPath("Position.Y");
            Ybinding.Mode = BindingMode.TwoWay;
            this.SetBinding(Map.TopProperty, Ybinding);
        }

        protected virtual void alterSpeed()
        {
            Random random = new Random();
            Speed = (random.NextDouble() >= 0.5) ? Speed + random.Next(2) : Speed - random.Next(2);
        }

        /***************
         * POSITIONING *
         ***************/
        private Rect trueBounds;

        /**
         * Method to update the true bounds-variable (reflects the TRUE bounds this car is located on, takes all transforms into account)
         * */
        public void updateTrueBounds(Rect newBounds)
        {
            trueBounds = newBounds;
        }

        /**
         * Method to retrieve the true bounds
         * */
        public Rect getTrueBounds()
        {
            return trueBounds;
        }

        private int getAdjustedXOrientation()
        {
            if (Orientation == 0 || Orientation == 180) return 0;
            if (Orientation > 0 && Orientation < 45) return Orientation;
            if (Orientation >= 45 && Orientation <= 135) return 45;
            if (Orientation < 180 && Orientation > 135) return 45 - (Orientation - 135);
            if (Orientation > 180 && Orientation < 225) return 180 - Orientation;
            if (Orientation > 225 && Orientation < 305) return -45;
            else return Orientation - 360;
        }

        private int getAdjustedYOrientation()
        {
            if (Orientation == 90 || Orientation == 270) return 0;
            if (Orientation <= 45) return -45;
            if (Orientation > 45 && Orientation < 90) return (90 - Orientation) * -1;
            if (Orientation > 90 && Orientation < 135) return Orientation - 90;
            if (Orientation >= 135 && Orientation <= 225) return 45;
            if (Orientation > 225 && Orientation < 270) return 270 - Orientation;
            if (Orientation > 270 && Orientation < 305) return -45 + (305 - Orientation);
            else return -45;
        }

        /**
         * Get the point the frontbumper of this car is located at (return the centerpoint of the frontbumper)
         * */
        public Point getFrontBumperPoint()
        {
            Point bumperPoint = new Point();
            double centerX = (trueBounds.Left + trueBounds.Right) / 2, centerY = (trueBounds.Top + trueBounds.Bottom) / 2;
            double adjustedXOrientation = getAdjustedXOrientation(), adjustedYOrientation = getAdjustedYOrientation();
            bumperPoint.X = centerX + ((((trueBounds.Width) / 2) / 45) * adjustedXOrientation);
            bumperPoint.Y = centerY + ((((trueBounds.Height) / 2) / 45) * adjustedYOrientation);
            return bumperPoint;
        }

        /**
         * Get the point the rearbumper of this car is located at (return the centerpoint of the rearbumper)
         * */
        public Point getRearBumperPoint()
        {
            Point bumperPoint = new Point();
            double centerX = (trueBounds.Left + trueBounds.Right) / 2, centerY = (trueBounds.Top + trueBounds.Bottom) / 2;
            double adjustedXOrientation = getAdjustedXOrientation() * -1, adjustedYOrientation = getAdjustedYOrientation() * -1;
            bumperPoint.X = centerX + ((((trueBounds.Width) / 2) / 45) * adjustedXOrientation);
            bumperPoint.Y = centerY + ((((trueBounds.Height) / 2) / 45) * adjustedYOrientation);
            return bumperPoint;
        }

        /**
         * Get the centerpoint of this car
         * */
        public Point getTrueCenter()
        {
            return new Point((trueBounds.Left + trueBounds.Right) / 2, (trueBounds.Top + trueBounds.Bottom) / 2);
        }

        private static Point newPosition(Point oldPoint, int orientation, double speed)
        {
            return new Point(oldPoint.X + (speed * Math.Sin(degreeToRadian(orientation))),
                oldPoint.Y - (speed * Math.Cos(degreeToRadian(orientation))));
        }

        private static double degreeToRadian(double angle)
        {
            return Math.PI * angle / 180.0;
        }

        /*********************
         * THREAD MANAGEMENT *
         *********************/
        private Thread driveThread = null;

        /**
         * Method to start the drive-thread
         * */
        public void startDriving()
        {
            driveThread = new Thread(drive);
            driveThread.Start();
        }

        /**
         * Method to stop the drive-thread
         * */
        public void stopDriving()
        {
            if (driveThread == null) return;
            driveThread.Abort();
            driveThread = null;
        }

        /************
         * MOVEMENT *
         ************/
        private CarStatus carStatus;
        private TrafficLight nextTrafficLight;
        private TrafficMarshal trafficMarshal;
        private Direction nextDirection, switchLaneDirection;
        private Lane currentLane, wantedLane, oldLane;
        private Point nextDestination;
        private Boolean hasDestination, watchTrafficMarshal, onIntersection, mustSwitchLane, turnSignalIsOn;
        private double totalDistance, xDistance, yDistance, maxSpeed, effectiveSpeed;
        private int oldOrientation, newOrientation;

        private System.Timers.Timer timerTurnSignal;

        /**
         * Method to retrieve this cars current status
         * */
        public CarStatus getStatus()
        {
            return carStatus;
        }

        private void initDrive() //prepare all variables
        {
            nextDirection = Toolkit.RandomEnum<Direction>();
            nextDestination = new Point();
            hasDestination = false;
            watchTrafficMarshal = false;
            turnSignalIsOn = false;
            onIntersection = false;
            mustSwitchLane = false;
            maxSpeed = -1;
            timerTurnSignal = new System.Timers.Timer(200);
            timerTurnSignal.Elapsed += new ElapsedEventHandler(turnSignals);
        }

        /**
         * Set the initial orientation
         * */
        public void setInitialNewOrientation(int newOrientation)
        {
            this.newOrientation = newOrientation;
        }

        private void drive()
        {
            try
            {
                initDrive();
                while (true)
                {
                    DateTime startTime = DateTime.Now; //get starttime
                    Map.INSTANCE.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(doDrive)); //drive
                    updateBounds(); //update bounds
                    DateTime stopTime = DateTime.Now; //get endtime
                    Thread.Sleep(TimeSpan.FromMilliseconds(sleepTime) - (startTime - stopTime)); //sleep
                }
            }
            catch (ThreadAbortException e)
            {
                Trace.WriteLine("Car was stopped :(");
            }
        }

        private void doDrive()
        {
            updateDestination(); //check if the destination needs to be updated (check if we entered a new lane or intersection)
            effectiveSpeed = (maxSpeed != -1 && maxSpeed < Speed) ? maxSpeed : Speed; //set the effective speed (= Speed limited by maxSpeed)
            checkTrafficLight(); //check if we need to wait for a traffic light
            checkTrafficMarshal(); //check if we need to wait for a traffic marshal
            checkCars(); //check if we need to wait for another car
            checkCarsOnOtherLane(); //check if we need to wait for another car located at anotherlane
            checkRightLane(); //check if we can move the right-hand lane
            changePosition(); //update our position (drive)
        }

        private void checkRightLane() //check if we can move the right-hand lane
        {
            if (currentLane != null && mustSwitchLane == false)
            {
                 Lane newLane = RightLane(currentLane);
                if (currentLane != newLane)
                {
                    if (Lane.laneIsNextToLane(newLane, currentLane) //cannot switch to lane that is not next to this lane
                            && laneIsLongEnough(newLane, 2) //cannot switch to lane that is not long enough
                            && (laneIsClear(newLane, 1.50, 0.5))
                            && (newLane.isARamp == 0)) 
                    {
                     
                        switchLane(newLane);
                    }
                }
            }
        }

        private void resetCarState(Boolean cameFromIntersection) //resets the car state
        {
            hasDestination = false;
            nextTrafficLight = null;
            if (cameFromIntersection)
                leftInterSection();
            onIntersection = false;
            currentLane = null;
            wantedLane = null;
            mustSwitchLane = false;
            switchLaneDirection = Direction.FORWARD;
        }

        private void leftInterSection()//car has left intersection
        {
            removeFromTrafficMarshal();
            nextDirection = Toolkit.RandomEnum<Direction>();
            stopSignals();
        }

        private void removeFromTrafficMarshal()//remove this car from the traffic marshal
        {
            if (watchTrafficMarshal)
            {
                trafficMarshal.removeCar(this);
                watchTrafficMarshal = false;
                trafficMarshal = null;
            }
        }

        private void updateDestination() //check if the destination needs to be updated (check if we entered a new lane or intersection)
        {
            if (hasDestination)
            {
                Rect bounds = getTrueBounds();
                if (laneToLane())
                    resetCarState(false);
                else if (interSectionToLane())
                    resetCarState(true);
                else if (laneToInterSection())
                    resetCarState(false);
                else return;
            }
            if (!getDestinationFromIntersection())
                getDestinationFromLane();
            oldOrientation = Orientation;
        }

        private Boolean laneToLane() //car came from a lane and entered a new lane
        {
            Lane newLane = Map.INSTANCE.getLaneAtPoint(getFrontBumperPoint());
            return (currentLane != null && newLane != null && currentLane != newLane && Orientation == newLane.Orientation);
        }

        private Boolean laneToInterSection() //car came from a lane and entered a intersection
        {
            return (Map.INSTANCE.getInterSectionAtPoint(getFrontBumperPoint()) != null && !onIntersection);
        }

        private Boolean interSectionToLane() //car came from a intersection and entered a lane
        {
            if (onIntersection &&
                Map.INSTANCE.getInterSectionAtPoint(getFrontBumperPoint()) == null &&
                Map.INSTANCE.getInterSectionAtPoint(getTrueCenter()) == null)
            {
                Lane newLane = Map.INSTANCE.getLaneAtPoint(getRearBumperPoint());
                if (newLane != null && newLane.Orientation == Orientation)
                    return true;
                else if (newLane != null && Toolkit.getOrientationDifference(Orientation, newLane.Orientation) < 4)
                {
                    snapToLane(newLane);
                    return true;
                }
            }
            return false;
        }

        private Boolean getDestinationFromIntersection() //check if we can retrieve the next destination from the intersection
        {
            InterSection interSection = Map.INSTANCE.getInterSectionAtPoint(getFrontBumperPoint());
            if (interSection != null)
            {
                hasDestination = true;
                Lane destinationLane = interSection.getExitLane(new DirectionData(newOrientation, nextDirection), this);
                updateDestination(destinationLane.getStartPoint());
                maxSpeed = destinationLane.MaxSpeed;
                newOrientation = destinationLane.Orientation;
                onIntersection = true;
                setTrafficMarshal(interSection.getTrafficMarshal());
                return true;
            }
            return false;
        }

        private Side findNewSide() //method to find the side this car is moving towards
        {
            if ((Orientation == 90 && nextDirection == Direction.LEFT) ||
                (Orientation == 270 && nextDirection == Direction.RIGHT) ||
                (Orientation == 0 && nextDirection == Direction.FORWARD))
                return Side.TOP;
            else if ((Orientation == 90 && nextDirection == Direction.RIGHT) ||
                (Orientation == 270 && nextDirection == Direction.LEFT) ||
                (Orientation == 180 && nextDirection == Direction.FORWARD))
                return Side.BOTTOM;
            else if ((Orientation == 0 && nextDirection == Direction.RIGHT) ||
                (Orientation == 180 && nextDirection == Direction.LEFT) ||
                (Orientation == 90 && nextDirection == Direction.FORWARD))
                return Side.RIGHT;
            else return Side.LEFT;
        }

        private void getDestinationFromLane() //method to check if we can retrieve a new destination from a lane
        {
            Lane lane = Map.INSTANCE.getLaneAtPoint(getFrontBumperPoint());
            if (lane != null && correctLane(lane))
            {
                hasDestination = true;
                updateDestination(lane.getDestinationPoint());
                maxSpeed = lane.MaxSpeed;
                newOrientation = lane.Orientation;
                currentLane = lane; oldLane = lane;
                if (lane.hasTrafficLight())
                {
                    nextTrafficLight = lane.trafficLight;
                    startSignals();
                }
            }
        }

        private Boolean correctLane(Lane lane) //check if the current lane is the correct lane, switch lanes if this is not the case
        {
            if (lane.getAllowedDirections().Contains(nextDirection)) //check if current lane allows next direction
                return true;
            Road currentRoad = Map.INSTANCE.getRoadAtPoint(Toolkit.getCenterPoint(lane));
            List<Direction> allowedDirections = getAllowedDirectionsFromRoad(currentRoad);
            if (!allowedDirections.Contains(nextDirection))
            {
                generateNewNextDirection(allowedDirections);
                getDestinationFromLane();
                return false;
            }
            currentLane = lane;
            switchLane(getCorrectLane(currentRoad));
            return false;
        }

        private Lane RightLane(Lane lane) //retrieve the right-hand lane this cars needs to move to
        {
           Road currentRoad = Map.INSTANCE.getRoadAtPoint(Toolkit.getCenterPoint(lane));
           Lane righLane = Road.getRightLaneFromLanes(currentRoad.getLanesWithAllowedDirection(nextDirection));
           if (Lane.laneIsNextToLane(righLane, currentLane) || lane == righLane)
           {
               return righLane;
           }
           else if (currentRoad != null)
           {
               Lane closeLane = currentRoad.getClosestLaneWithDirection(righLane, nextDirection);
               if (closeLane != null && Lane.laneIsNextToLane(closeLane, currentLane))
               {
                   return closeLane;
               }
               else
               {
                   closeLane = currentRoad.getClosestLaneWithDirection(currentLane, nextDirection);
                   if (closeLane != null && Lane.laneIsNextToLane(closeLane, currentLane))
                   {
                       return closeLane;
                   }
               }
           }
           return righLane;
        }

        public void generateNewNextDirection(List<Direction> allowedDirections) //generate a new nextDirection from the options given in 'allowedDirections'
        {
            while (!allowedDirections.Contains(nextDirection))
                nextDirection = Toolkit.RandomEnum<Direction>();
        }

        private List<Direction> getAllowedDirectionsFromRoad(Road road) //retrieve all allowed directions from a road
        {
            List<Direction> allowedDirections = new List<Direction>();
            foreach (Lane lane in road.getLanes())
                foreach (Direction direction in lane.getAllowedDirections())
                    if (!allowedDirections.Contains(direction))
                        allowedDirections.Add(direction);
            return allowedDirections;
        }

        private Lane getCorrectLane(Road road) //retrieve the correct lane
        {
            return road.getClosestLaneWithDirection(currentLane, nextDirection);
        }

        private void switchLane(Lane lane) //switch lanes
        {
            hasDestination = true;
            newOrientation = lane.Orientation;
            updateDestination(getSwitchLaneDestination(lane));
            maxSpeed = lane.MaxSpeed;
            if (lane.hasTrafficLight())
                nextTrafficLight = lane.trafficLight;
            mustSwitchLane = true;
            wantedLane = lane;
            startSignals();
        }
      
        private Point getSwitchLaneDestination(Lane lane) //get the next destination (when switching lanes)
        {
            Point lanePoint = Toolkit.getCenterPoint(lane);
            Point carPoint = getTrueCenter();
            switch (newOrientation)
            {
                case 0:
                    lanePoint.Y = getTrueCenter().Y + (Toolkit.getBounds(this).Height * 1.5); break;
                case 90:
                    lanePoint.X = getTrueCenter().X + (Toolkit.getBounds(this).Width * 1.5); break;
                case 180:
                    lanePoint.Y = getTrueCenter().Y - (Toolkit.getBounds(this).Height * 1.5); break;
                case 270:
                    lanePoint.X = getTrueCenter().X - (Toolkit.getBounds(this).Width * 1.5); break;
                default: throw new InvalidOperationException("newOrientation was invalid");
            }
            switchLaneDirection = getSwitchLaneDirection(carPoint, lanePoint);
            return lanePoint;
        }

        private string getCurrentCarType() //get current car type (in image-string format)
        { 
            string carType = "cara";
            if (Source.ToString().Contains("cara")) { carType = "cara"; }
            else if (Source.ToString().Contains("carb")) { carType = "carb"; }
            else if (Source.ToString().Contains("carc")) { carType = "carc"; }
            else if (Source.ToString().Contains("card")) { carType = "card"; }
            else if (Source.ToString().Contains("care")) { carType = "care"; }
            else if (Source.ToString().Contains("carf")) { carType = "carf"; }
            else if (Source.ToString().Contains("carg")) { carType = "carg"; }
            else if (Source.ToString().Contains("carh")) { carType = "carh"; }
            else if (Source.ToString().Contains("cari")) { carType = "cari"; }
            else if (Source.ToString().Contains("carj")) { carType = "carj"; }
            else if (Source.ToString().Contains("cark")) { carType = "cark"; }
            return carType;
        }

        private Direction getSwitchLaneDirection(Point carPoint, Point lanePoint) //get the direction we're switching lanes in
        {
            Direction direction = Direction.FORWARD;
            switch (newOrientation)
            {
                case 0:
                    direction = (carPoint.X > lanePoint.X) ? Direction.LEFT : Direction.RIGHT; break;
                case 90:
                    direction = (carPoint.Y > lanePoint.Y) ? Direction.LEFT : Direction.RIGHT; break;
                case 180:
                    direction = (carPoint.X > lanePoint.X) ? Direction.RIGHT : Direction.LEFT; break;
                case 270:
                    direction = (carPoint.Y > lanePoint.Y) ? Direction.RIGHT : Direction.LEFT; break;
            }
            return direction;
        }

        private void startSignals() //start the turnsignals-timer
        {
            timerTurnSignal.Enabled = true;
        }

        private void stopSignals() //stop the turnsignals-timer
        {
            timerTurnSignal.Enabled = false;
            turnSignalsOff();
        }

        private void turnSignalsOn() //blink the signals
        {
            if (timerTurnSignal.Enabled)
            {
                if (currentLane != null && currentLane.hasTrafficLight())
                {
                    if (nextDirection == Direction.LEFT)
                        Source = new BitmapImage(new Uri(@"/Master;component/Images/" + getCurrentCarType() + "left.png", UriKind.Relative));
                    else if (nextDirection == Direction.RIGHT)
                        Source = new BitmapImage(new Uri(@"/Master;component/Images/" + getCurrentCarType() + "right.png", UriKind.Relative));
                    turnSignalIsOn = true;
                }
                else
                { 
                    if (switchLaneDirection == Direction.LEFT)
                        Source = new BitmapImage(new Uri(@"/Master;component/Images/" + getCurrentCarType() + "left.png", UriKind.Relative));
                    else if (switchLaneDirection == Direction.RIGHT)
                        Source = new BitmapImage(new Uri(@"/Master;component/Images/" + getCurrentCarType() + "right.png", UriKind.Relative));
                    turnSignalIsOn = true;
                }
            }
        }

        private void turnSignalsOff() //stop the turnsignals
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/" + getCurrentCarType() + ".png", UriKind.Relative));
            turnSignalIsOn = false;
        }

        private void turnSignals(object source, ElapsedEventArgs e) //blink the turn signals
        {
            this.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate()
            {
                if (turnSignalIsOn) turnSignalsOff();
                else turnSignalsOn();
            }));
        }

        private void updateDestination(Point newDestination) //update the next destination
        {
            nextDestination = newDestination;
            Point centerPoint = getTrueCenter();
            xDistance = getXDistance(centerPoint, newDestination);
            yDistance = getYDistance(centerPoint, newDestination);
            totalDistance = xDistance + yDistance;
        }

        private void checkTrafficLight() //check if this car needs to wait for a traffic light
        {
            if (nextTrafficLight == null) return;
            Point trafficLightLocation = Toolkit.getPointRelativeToMap(nextTrafficLight);
            Point carCenterLocation = getTrueCenter();
            double distance = 0; double carSize = 0;
            if (newOrientation == 0 || newOrientation == 180)
            {
                distance = trafficLightLocation.Y > carCenterLocation.Y ? trafficLightLocation.Y - carCenterLocation.Y : carCenterLocation.Y - trafficLightLocation.Y;
                carSize = getTrueBounds().Height;
            }
            else
            {
                distance = trafficLightLocation.X > carCenterLocation.X ? trafficLightLocation.X - carCenterLocation.X : carCenterLocation.X - trafficLightLocation.X;
                carSize = getTrueBounds().Width;
            }
            if (distance <= (carSize + maxSpeed) && distance >= carSize)
            {
                setTrafficMarshal(Map.INSTANCE.getLaneAtPoint(trafficLightLocation).getInterSection().getTrafficMarshal());
                atTrafficLight(trafficLightLocation);
            }
        }

        private void atTrafficLight(Point trafficLightLocation) //check if this car is at a traffic light
        {
            if (nextTrafficLight.Status == TrafficLightStatus.RED || nextTrafficLight.Status == TrafficLightStatus.ORANGE)
            {
                effectiveSpeed = 0;
                carStatus = CarStatus.WAITINGFORTRAFFICLIGHT;
                return;
            }
        }
         
        private void setTrafficMarshal(TrafficMarshal trafficMarshal) //method to 'set' a traffic marshal
        {
            if (!watchTrafficMarshal && trafficMarshal != null)
            {
                this.trafficMarshal = trafficMarshal;
                trafficMarshal.registerCar(new DirectionData(Orientation, nextDirection), this);
                watchTrafficMarshal = true;
            }
        }

        private void checkTrafficMarshal() //check if this car needs to wait for a traffic marshal
        {
            if (!watchTrafficMarshal) return;
            if (onIntersection && Map.INSTANCE.getIntersectionAtBounds(getTrueBounds()) == null)
            {
                removeFromTrafficMarshal();
                return;
            }
            if (effectiveSpeed <= 0) return;
            if (!trafficMarshal.canMoveForward(this))
            {
                effectiveSpeed = 0;
                carStatus = CarStatus.WAITINGFORMARSHAL;
            }
        }

        private void checkCars() //check if this car needs to wait on another car
        {
            if (effectiveSpeed == 0) return; //already stopped, no need to check for cars
            double distance = getCheckCarsDistance(); // distance needed between cars
            List<Car> cars = Map.INSTANCE.getCarsAtBounds(getCarDetectionBounds(distance));
            if (cars.Count == 0) return;
            else foreach (Car car in cars) if (car != this)
            {
                if (car != null && 
                    !(Toolkit.getOrientationDifference(newOrientation, car.newOrientation) == 180 && Toolkit.getOrientationDifference(oldOrientation, car.oldOrientation) == 180) 
                    && !(isBehindThisCar(car) && (carStatus == CarStatus.WAITINGFORCARONOTHERLANE || carStatus == CarStatus.WAITINGFORCAR))
                    && !(onIntersection && car.onIntersection && car.nextDirection == nextDirection && sameRoadDifferentLane(car.oldLane, oldLane) && nextDestination != car.nextDestination)
                    && !(car.onIntersection && oldLane.Orientation != car.oldLane.Orientation && !(oldLane.Orientation == Map.INSTANCE.getLaneAtPoint(car.nextDestination).Orientation)))
                {
                    if (!canOvertake(car))
                    {
                        effectiveSpeed = 0;
                        carStatus = CarStatus.WAITINGFORCAR;
                    }
                   
                    return;
                }
            }
        }

        private Boolean canOvertake(Car car) //check if this car can overtake the given car (and overtake if that is the case)
        {
            if (currentLane == null || car.onIntersection) return false; //currently not on a lane
            Road road = Map.INSTANCE.getRoadAtPoint(getFrontBumperPoint());
            if (road == null) return false;
            List<Lane> lanesWithCorrectDirection = road.getLanesWithAllowedDirection(nextDirection).ToList();
            if (lanesWithCorrectDirection.Count <= 1) return false; //there is no other lane with the wanted direction
            lanesWithCorrectDirection.Remove(currentLane);
            foreach (Lane lane in lanesWithCorrectDirection) 
                if (Lane.laneIsNextToLane(lane, currentLane) //cannot switch to lane that is not next to this lane
                    && laneIsLongEnough(lane, 2) //cannot switch to lane that is not long enough
                    && (laneIsClear(lane, 1.50, 0.5))) //check if there is room on this lane
            {
                switchLane(lane);
                return true;
            }
            return false;
        }

        private Boolean laneIsLongEnough(Lane lane, double distanceFactor) //check if the specified lane is long enough (dont switch to lanes that are about to end)
        {
            Rect bounds = getTrueBounds();
            Point lanePoint = Toolkit.getCenterPoint(lane);
            Lane pointLane = null;
            switch (newOrientation)
            {
                case 0:
                    pointLane = Map.INSTANCE.getLaneAtPoint(new Point(lanePoint.X, bounds.Top - getTrueBounds().Height * distanceFactor)); break;
                case 90:
                    pointLane = Map.INSTANCE.getLaneAtPoint(new Point(bounds.Right + getTrueBounds().Width * distanceFactor, lanePoint.Y)); break;
                case 180:
                    pointLane = Map.INSTANCE.getLaneAtPoint(new Point(lanePoint.X, bounds.Bottom + getTrueBounds().Height * distanceFactor)); break;
                case 270:
                    pointLane = Map.INSTANCE.getLaneAtPoint(new Point(bounds.Left - getTrueBounds().Width * distanceFactor, lanePoint.Y)); break;
                default: throw new InvalidOperationException("Invalid newOrientation");
            }
            return (pointLane != null && pointLane == lane);
        }

        private Boolean sameRoadDifferentLane(Lane firstLane, Lane secondLane) //check if both lanes are different lanes located on the same road
        {
            return (firstLane != secondLane &&
            Map.INSTANCE.getRoadAtPoint(Toolkit.getCenterPoint(firstLane)) == Map.INSTANCE.getRoadAtPoint(Toolkit.getCenterPoint(secondLane)));
        }

        private double getCheckCarsDistance() //generate the distance we should check for other cars
        {
            double distance = 0;
            if (Orientation == 270 || Orientation == 90)
                distance = getTrueBounds().Width * (0.6);
            else if (Orientation == 0 || Orientation == 180)
               distance = getTrueBounds().Height * (0.6);
            else distance = (getTrueBounds().Height + getTrueBounds().Width) / 4;
            return distance;
        }

        private Rect getCarDetectionBounds(double distance) //generate the bounds in which we should check for other cars
        {
            Rect bounds = Toolkit.cloneBoundsCenteredAtPoint(getTrueBounds(), newPosition(getTrueCenter(), Orientation, distance));
            if (wantedLane != null) return limitBoundsByWantedLane(bounds);
            if (onIntersection && Toolkit.pointWithinBounds(getTrueBounds(), nextDestination)) return limitBoundsByDestinationLane(bounds);
            if (onIntersection) return alterBoundsWhileOnIntersection(bounds);
            return bounds;
        }

        private Rect limitBoundsByWantedLane(Rect bounds) //limit the given bounds by the wantedlane
        {
            Rect laneBounds = Toolkit.getBounds(wantedLane);
            return Toolkit.limitBoundsByBounds(bounds, laneBounds);
        }

        private Rect limitBoundsByDestinationLane(Rect bounds) //limit the given bounds by the destinaionlane
        {
            Rect laneBounds = Toolkit.getBounds(Map.INSTANCE.getLaneAtPoint(nextDestination));
            return Toolkit.limitBoundsByBounds(bounds, laneBounds);
        }

        private Rect alterBoundsWhileOnIntersection(Rect bounds) //alter the bounds when located on a intersection (do not check for cars on lanes next to the destination lane)
        {
            Point startPoint = bounds.TopLeft, endPoint = bounds.BottomRight;
            if (oldOrientation == 90 && (newOrientation == 180 || newOrientation == 0))
            {
                double x = Toolkit.getBounds(Map.INSTANCE.getLaneAtPoint(nextDestination)).Right;
                if (endPoint.X > x) endPoint.X = x;
            }
            else if (oldOrientation == 270 && (newOrientation == 180 || newOrientation == 0))
            {
                double x = Toolkit.getBounds(Map.INSTANCE.getLaneAtPoint(nextDestination)).Left;
                if (startPoint.X < x) startPoint.X = x;
            }
            else if (oldOrientation == 0 && (newOrientation == 90 || newOrientation == 270))
            {
                double y = Toolkit.getBounds(Map.INSTANCE.getLaneAtPoint(nextDestination)).Top;
                if (startPoint.Y < y) startPoint.Y = y;
            }
            else if (oldOrientation == 180 && (newOrientation == 90 || newOrientation == 270))
            {
                double y = Toolkit.getBounds(Map.INSTANCE.getLaneAtPoint(nextDestination)).Bottom;
                if (endPoint.Y > y) endPoint.Y = y;
            }
            return new Rect(startPoint, endPoint);
        }

        private void checkCarsOnOtherLane() //check for cars on other lanes
        {
            if (effectiveSpeed == 0 || !mustSwitchLane || wantedLane == null) return; //already stopped or not switching lanes, no need to check for cars
            if (!laneIsClear(wantedLane, 0.5, 0.25))
            {
                effectiveSpeed = 0;
                carStatus = CarStatus.WAITINGFORCARONOTHERLANE;
            }
        }

        private Boolean laneIsClear(Lane lane, double checkFrontDistanceFactor, double checkBehindDistanceFactor) //check if the given lane is clear
        {
            Rect bounds = new Rect(), laneBounds = Toolkit.getBounds(lane);
            switch (newOrientation)
            {
                case 0:
                    bounds = new Rect(new Point(laneBounds.Left, getTrueBounds().Top - (getTrueBounds().Height * checkFrontDistanceFactor)),
                        new Point(laneBounds.Right, getTrueBounds().Bottom + (getTrueBounds().Height * checkBehindDistanceFactor))); break;
                case 90:
                    bounds = new Rect(new Point(getTrueBounds().Right + (getTrueBounds().Width * checkFrontDistanceFactor), laneBounds.Top),
                        new Point(getTrueBounds().Left - (getTrueBounds().Width * checkBehindDistanceFactor), laneBounds.Bottom)); break;
                case 180:
                    bounds = new Rect(new Point(laneBounds.Left, getTrueBounds().Bottom + (getTrueBounds().Height * checkFrontDistanceFactor)),
                        new Point(laneBounds.Right, getTrueBounds().Top - (getTrueBounds().Height * checkBehindDistanceFactor))); break;
                case 270:
                    bounds = new Rect(new Point(getTrueBounds().Left - (getTrueBounds().Width * checkFrontDistanceFactor), laneBounds.Top),
                        new Point(getTrueBounds().Right + (getTrueBounds().Width * checkBehindDistanceFactor), laneBounds.Bottom)); break;
                default: throw new InvalidOperationException("newOrientation was invalid");
            }
            return laneIsClear(bounds);
        }

        private Boolean laneIsClear(Rect bounds) //check if the given lane is clear
        {
            List<Car> cars = Map.INSTANCE.getCarsAtBounds(bounds);
            if (cars.Contains(this)) cars.Remove(this);
            List<Car> ignoredCars = new List<Car>();
            foreach (Car car in cars)
                if (Toolkit.getOrientationDifference(newOrientation, car.newOrientation) == 180)
                    ignoredCars.Add(car);
            foreach (Car nextCar in cars)
                if ((nextCar.carStatus == CarStatus.WAITINGFORCARONOTHERLANE ||
                    nextCar.carStatus == CarStatus.WAITINGFORCAR)
                    && isBehindThisCar(nextCar) && !ignoredCars.Contains(nextCar))
                    ignoredCars.Add(nextCar);
            foreach (Car nextCar in ignoredCars)
                cars.Remove(nextCar);
            return (cars.Count == 0);
        }

        private Boolean isBehindThisCar(Car car) //check if the current car is located behind the given car
        {
            if ((newOrientation == 0 && car.getTrueBounds().Top >= getTrueBounds().Top) ||
               (newOrientation == 90 && car.getTrueBounds().Right <= getTrueBounds().Right) ||
                (newOrientation == 180 && car.getTrueBounds().Bottom <= getTrueBounds().Bottom) ||
                (newOrientation == 270 && car.getTrueBounds().Left >= getTrueBounds().Left))
                return true;
            return false;
        }

        private void snapToLane(Lane lane) //snap to the specified lane (centers this car at the given lane)
        {
            Point currentCenterPoint = getTrueCenter();
            Point wantedCenterPoint = new Point(currentCenterPoint.X, currentCenterPoint.Y);
            if (lane.Orientation == 90 || lane.Orientation == 270)
            {
                Point laneLocation = Toolkit.getPointRelativeToMap(lane);
                wantedCenterPoint.Y = ((laneLocation.Y * 2) + lane.ActualHeight) / 2;
            }
            else
            {
                Point laneLocation = Toolkit.getPointRelativeToMap(lane);
                wantedCenterPoint.X = ((laneLocation.X * 2) + lane.ActualWidth) / 2;
            }
            Orientation = lane.Orientation;
            Position = new Point(Position.X + (wantedCenterPoint.X - currentCenterPoint.X), Position.Y + (wantedCenterPoint.Y - currentCenterPoint.Y));
        }

        private void changePosition() //update the current position (drive)
        {
            if (effectiveSpeed <= 0) return;
            carStatus = CarStatus.DRIVING;
            if (mustSwitchLane)
                doSwitchLane();
            else if ((!hasDestination || newOrientation == oldOrientation)) //no destination set or new destination is straight ahead
                Position = newPosition(Position, Orientation, effectiveSpeed);
            else
                moveTowardDestination();
        }

        private void doSwitchLane() //update the current position while switching lanes
        {
            double distanceFactor = 0;
            if (newOrientation == 180 || newOrientation == 0)
                distanceFactor = getXDistance(getTrueCenter(), nextDestination) / xDistance;
            else
                distanceFactor = getYDistance(getTrueCenter(), nextDestination) / yDistance;
            Orientation = generateSwitchLaneOrientation(distanceFactor);
            Position = newPosition(Position, Orientation, effectiveSpeed);
        }

        private QuinticEase slEase = new QuinticEase(); //switch lane ease

        private int generateSwitchLaneOrientation(double distanceFactor) //generate the next orientation while switching lanes
        {
            double currentDifference = Toolkit.getOrientationDifference(newOrientation, Orientation);
            int directionFactor = (switchLaneDirection == Direction.LEFT) ? -1 : 1;
            if (distanceFactor >= 0.5) 
                currentDifference = (currentDifference + (Speed / 2) > 45) ? 45 : currentDifference + (Speed / 2);
            else
            {
                distanceFactor *= 2;
                slEase.EasingMode = EasingMode.EaseOut;
                currentDifference = slEase.Ease(distanceFactor) * 45;
            }
            return Toolkit.alterOrientationBy(newOrientation, (int) (currentDifference * directionFactor));
        }
        
        private double getXDistance(Point a, Point b) //get x distance between given points
        {
            double xDistance = a.X - b.X;
            if (xDistance < 0) xDistance *= -1;
            return xDistance;
        }

        private double getYDistance(Point a, Point b) //get y distance between given points
        {
            double yDistance = a.Y - b.Y;
            if (yDistance < 0) yDistance *= -1;
            return yDistance;
        }

        private double getTotalDistance(Point a, Point b) //get total distance between given points
        {
            return getXDistance(a, b) + getYDistance(a, b);
        }

        private EasingFunctionBase m2Dease = new QuadraticEase(); //move 2 destination ease

        private void moveTowardDestination() //move towards a destination (make turn)
        {
            double xSpeed = 0, ySpeed = 0, currentDistance = 0, distanceEaseResult = 0;
            if (oldOrientation == 180 || oldOrientation == 0)
            {
                currentDistance = getYDistance(getTrueCenter(), nextDestination);
                distanceEaseResult = m2Dease.Ease(currentDistance / yDistance);
            }
            else
            {
                currentDistance = getXDistance(getTrueCenter(), nextDestination);
                distanceEaseResult = m2Dease.Ease(currentDistance / xDistance);
            }
            xSpeed = getXSpeed(distanceEaseResult);
            ySpeed = getYSpeed(distanceEaseResult);
            generateNewOrientation(distanceEaseResult);
            Position = new Point(Position.X + xSpeed, Position.Y + ySpeed);
        }

        private double getXSpeed(double easeResult) //get current X-speed
        {
            if (oldOrientation == 90)
                return effectiveSpeed * easeResult;
            if (oldOrientation == 270)
                return (effectiveSpeed * easeResult) * -1;
            if (newOrientation == 90)
                return effectiveSpeed * (1 - easeResult);
            else
                return (effectiveSpeed * (1 - easeResult)) * -1;
        }

        private double getYSpeed(double easeResult) //get current Y-speed
        {
            if (oldOrientation == 0) //car was moving towards the top of the screen
                return (effectiveSpeed * easeResult) * -1;
            if (oldOrientation == 180) //car was moving towards the bottom of the screen
                return effectiveSpeed * easeResult;
            if (newOrientation == 0) //car is turning towards the top of the screen
                return (effectiveSpeed * (1 - easeResult)) * -1;
            else //car is turning towards the bottom of the screen
                return (effectiveSpeed * (1 - easeResult));
        }

        private void generateNewOrientation(double easeResult)//generate the next orientation
        {
            if (nextDirection == Direction.RIGHT)
                Orientation = Toolkit.alterOrientationBy(oldOrientation, (int)(90 * (1 - easeResult)));
            else
                Orientation = Toolkit.alterOrientationBy(oldOrientation, (int)(-90 * (1 - easeResult)));
        }

        //force re-rendering (--> this ACTUALLY updates the position and re-draws the car with the new data) and draw hitbox
        private void updateBounds()
        {
            this.Dispatcher.Invoke(DispatcherPriority.Render,
                new Action(delegate()
                {
                    updateTrueBounds(getNewBounds(this));
                }
            ));
        }

        private Line[] lines, lines2;
        //get the new (true) bounds
        private static Rect getNewBounds(Car thisCar)
        {
            Rect bounds = Toolkit.getBounds(thisCar);
            return bounds;
        }
    }

    /**
     * Represents a red car
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class RedCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/cara.png", UriKind.Relative));
        }
    }

    /**
     * Represents a blue car
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class BlueCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/carb.png", UriKind.Relative));
        }
    }

    /**
     * Represents a pink car
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class PinkCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/carc.png", UriKind.Relative));
        }
    }

    /**
     * Represents a brown car
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class BrownCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/card.png", UriKind.Relative));
        }
    }

    /**
     * Represents a police car (has a higher average speed than regular cars)
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class PoliceCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/care.png", UriKind.Relative));
        }

        protected override void alterSpeed()
        {
            Random random = new Random();
            Speed = Speed + (random.Next(3) + 1);
        }
    }

    /**
     * Represents a green car
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class GreenCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/carf.png", UriKind.Relative));
        }
    }

    /**
     * Represents a yellow car
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class YellowCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/carg.png", UriKind.Relative));
        }
    }

    /**
     * Represents a yellow sports car (has a higher average speed than regular cars)
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class YellowSportsCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/carh.png", UriKind.Relative));
        }

        protected override void alterSpeed()
        {
            Random random = new Random();
            Speed = Speed + (random.Next(3) + 1);
        }
    }

    /**
     * Represents a red sports car (has a higher average speed than regular cars)
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class RedSportsCar : Car
    {
        protected override void initCarImage()
        {
            Source = new BitmapImage(new Uri(@"/Master;component/Images/cari.png", UriKind.Relative));
        }

        protected override void alterSpeed()
        {
            Random random = new Random();
            Speed = Speed + (random.Next(3) + 1);
        }
    }

    /**
     * Represents a white van (has a lower average speed than regular cars)
     * 
     * @Changes made by:
     *      - A.J. Janssen
     * */
    public class WhiteCar : Car
    {
        protected override void initCarImage()
        {
            Scale = 0.30;
            Source = new BitmapImage(new Uri(@"/Master;component/Images/carj.png", UriKind.Relative));
        }

        protected override void alterSpeed()
        {
            Random random = new Random();
            Speed = Speed - (random.Next(3) + 1);
        }
    }

    /**
     * CarFactory can be used to create cars of any/all available car-types.
     * 
     * Methodname's of methods that create new cars should always start with "create" and end with "Car".
     * Car-types created by methods that do not adhere to the above rule will not be included in the "createRandomCar"-method.
     * 
     * @Author: A.J. Janssen
     * */
    public class CarFactory
    {
        private static List<MethodInfo> methods;
        private static Random random = new Random();

        static CarFactory()
        {
            methods = new List<MethodInfo>();
            foreach (MethodInfo method in typeof(CarFactory).GetMethods())
            {
                if (method.Name == "createRandomCar" || method.Name == "createCar")
                    continue;
                if (method.Name.StartsWith("create") && method.Name.EndsWith("Car"))
                    methods.Add(method);
            }
        }

        /**
         * Method to instantiate a car of the given type
         * */
        public static Car createCar(Type type)
        {
            foreach (MethodInfo method in methods)
                if (method.Name.EndsWith(type.Name))
                    return (Car)method.Invoke(null, null);
            throw new InvalidOperationException("No car of type: " + type + " exists");
        }

        public static Car createRandomCar() { return (Car) methods[random.Next(0, methods.Count)].Invoke(null, null); }

        public static Car createRedCar() { return new RedCar(); }
        public static Car createBlueCar() { return new BlueCar(); }
        public static Car createPinkCar() { return new PinkCar(); }
        public static Car createBrownCar() { return new BrownCar(); }
        public static Car createPoliceCar() { return new PoliceCar(); }
        public static Car createGreenCar() { return new GreenCar(); }
        public static Car createYellowCar() { return new YellowCar(); }
        public static Car createYellowSportsCar() { return new YellowSportsCar(); }
        public static Car createRedSportsCar() { return new RedSportsCar(); }
        public static Car createWhiteCar() { return new WhiteCar(); }
    }
}